using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Xml.XPath;
using System.Reflection;
using System.Threading;
using System.Windows.Threading;
using System.Collections;
using System.Runtime.Remoting;

namespace EventAbstractionAPI.ApplicationObjectModel
{
    public class ApplicationObjectModel
    {
        /// <summary>
        /// This class constructs the Application Object Model and acts as a proxy for all messages to this package.
        /// It is likely to be the only public class in this package/namespace
        /// </summary>

        #region Initial Construction

        public ApplicationObjectModel(bool automatedConstruction)
        {
            if (automatedConstruction)
            {
                AutomatedContruction();
            }
            else
            {
                throw new Exception("Functionality for manual construction of Application Object Model not implemented yet");
            }

        }

        private void AutomatedContruction()
        {
            //declare the root object as Application
            SpecifyTree.Instance.SpecifyTreeRoot(Application.Current);

            SpecifyTree.Instance.addTier("View");
            SpecifyTree.Instance.addTier("Model");

            SpecifyTree.Instance.addTierChildren("Model", typeof(ModelAttribute));
            
            //declare the windows as the Root.View.children
            foreach (Window windowInstance in Application.Current.Windows)
            {
                this.addViewChild("View", windowInstance.Name, windowInstance);
            }

            //-------------------------------------------------
            //add the properties for every element in the tree.
            //-------------------------------------------------

            //App->View->Window(s)
            myTreeNodeClass node = Globals.Instance.ApplicationNode.FirstChild;

            AddPropertiesForViewItems(node);
        }

        /// <summary>
        /// Recurse the View node in the Application Object Model 
        /// </summary>
        /// <param name="node"></param>
        private void AddPropertiesForViewItems(myTreeNodeClass node)
        {
            //get the childern items and recurse them
            foreach (myTreeNodeClass childNode in node.Children)
	        {
                AddPropertiesForViewItems(childNode);
	        }

            //The view node is required as the input for this method but we will not want properties automatically generated for it.
            if (node.name == "View")
            {
                return;
            }

            //get the current element at the top of the tree and add the properties to it
            PropertyInfo[] properties = node.value.GetType().GetProperties();
            foreach (PropertyInfo property in properties)
            {
                node.Children.AddLast(new myTreeNodeClass(property.Name, property.GetValue(node.value, null), node));
            }
        }
 
        #endregion

        #region Runtime Constrution

        public void addTier(String name)
        {
            SpecifyTree.Instance.addTier(name);
        }

        /// <summary>
        /// Add a UI under the View branch of Object Model
        /// </summary>
        /// <param name="TierName">Use the constant string "View" as input, unless you are using a different name.</param>
        /// <param name="Childname"></param>
        /// <param name="instance"></param>
        public void addViewChild(String TierName, String Childname, Object instance)
        {
            SpecifyTree.Instance.addTierChild(TierName, Childname, instance);

            IEnumerator enumerator = LogicalTreeHelper.GetChildren((FrameworkElement)instance).GetEnumerator();
            
            //Define the typed iterator here so that it is not redefined in the loop below.
            FrameworkElement currentElement;

            while (enumerator.MoveNext())
            {
                try
                {
                    currentElement = (FrameworkElement)enumerator.Current;
                    addViewChild(Childname, currentElement.Name, currentElement);
                }
                catch (Exception)
                {
                }

            }

        }

        #endregion

        #region query

        private List<Object> getQueryResultSet(String query)
        {
            List<Object> list = new List<Object>();

            //get the query prefix to work out what type of query we are doing.
            String queryPrefix = query.Split(':')[0];

            //Get the query with the prefix removed
            String queryNoPrefix = query.TrimStart((queryPrefix+":").ToCharArray());

            //check to see what sort of query it is.
            switch((QueryType)Enum.Parse(typeof(QueryType), queryPrefix, true))
            {
                case QueryType.XPath:

                    XPathNodeIterator nodeList = SpecifyTree.Instance.XPathQuery(queryNoPrefix);
                    
                    while (nodeList.MoveNext())
                    {
                        list.Add(((EventAbstractionAPI.ApplicationObjectModel.XPathNav)(nodeList.Current)).getCurrentNode());
                        //list.Add(nodeList.Current.Value);
                    } 

                    break;

                default:
                    throw new Exception("Query type unknown, or not implemented yet");
            }

            return list;
        }

        /// <summary>
        /// Checks to see if the object is in the result set of the xpath query. Hence satisfying the limit set by the query.
        /// </summary>
        /// <param name="query">query to perform on application object model. The query requires a prefix to indicate the query type as multiple can potentially be supported. XPath is xpath:[query]</param>
        /// <param name="obj">check this object to see if it is in the result set from the xpath query</param>
        /// <returns>True if the object is in the result set of the xpath query otherwise false.</returns>
        public bool satisfies(String query, String name)
        {
            bool found = false;
            
            foreach (Object nodeID in getQueryResultSet(query))
            {
                
                if (((myTreeNodeClass)nodeID).name.EndsWith(name))
                {
                    found = true;
                    break;
                }
            }
 
            return found;
        }

        #endregion

        #region Execute

        /// <summary>
        /// Execute an action on a query result set.
        /// </summary>
        /// <param name="query">A query that is understood by the Application Object Model, such as Xpath. use format XPath://query</param>
        /// <param name="callType">This is an enumeration of type CallType.</param>
        /// <param name="call">This is the definition of the Calltype. ie if you selected method as calltype then enter the method name as string in this field.</param>
        /// <param name="args">This field is a string field that is a comma separated array. It takes the parameters for the method or property being used.</param>
        public void Execute(String query, CallType callType, String call, params Object[] args)
        {
            List<Object> resultsOfQuery = getQueryResultSet(query);

            if (callType == CallType.Method)
            {
                ExecuteMethod(resultsOfQuery, call, args);
            }
            else if (callType == CallType.Property)
            {
                ExecuteProperty(resultsOfQuery, call, args);
            }
            else
            {
                throw new Exception("Not Implemented yet. See Application Object Model.cs for details.");
            }

        }

        private void ExecuteMethod(List<Object> resultsOfQuery, String call, params Object[] args)
        {
            Object item, typedArgs;
            Type[] typeList = { typeof(string) };

            foreach (Object result in resultsOfQuery)
            {
                item = ((myTreeNodeClass)result).value;

                if (item.GetType().IsSubclassOf(typeof(FrameworkElement))) //if we have a component that belongs to the view tier
                {
                    ((FrameworkElement)item).Dispatcher.Invoke(DispatcherPriority.Normal,
                                (ThreadStart)delegate
                                {
                                    item.GetType().GetMethod(call).Invoke(item, args);
                                });
                }
                else //if we have a class that has a static method ...
                {
                    ((Type)(item)).GetMethod(call).Invoke(null, args);
                }


            }
        }

        private void ExecuteProperty(List<Object> resultsOfQuery, String call, params Object[] args)
        {
            //currently it is assumed args only has 1 value. 
            //In the future it is intended that args can have many values which will be treated as an array input.

            //Set up working variables for this method.
            Object item, typedArgs;
            Type[] typeList = { typeof(string) };

            foreach (Object result in resultsOfQuery)
            {
                item = ((myTreeNodeClass)result).value;

                if (item.GetType().IsSubclassOf(typeof(FrameworkElement))) //if we have a component that belongs to the view tier
                {

                    ((FrameworkElement)item).Dispatcher.Invoke(DispatcherPriority.Normal, 
                                                (ThreadStart)delegate 
                                                {
                                                    item.GetType().GetProperty(call).SetValue(item, args[0], null); 
                                                });
                }
                else //if we have a class that has a static property ...
                {
                    ((Type)(item)).GetMethod(call).Invoke(null, args);
                }
            }
        }

        #endregion

        #region Member Types

        public enum CallType { Method, Property };
        enum QueryType { XPath, LINQ }; //LINQ is something that could be done in the future. 

        #endregion


    }
}
